package team.seekers.hula.common.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import team.seekers.hula.common.entity.UserSessionDTO;
import team.seekers.hula.common.entity.result.ApiException;
import team.seekers.hula.common.entity.result.ApiExceptionEnum;
import team.seekers.hula.user.user.entity.dao.UserDO;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * date: 2021/5/24 23:20
 *
 * @author GH
 */
@Slf4j
@Component
public class JwtUtil {

    @Autowired
    private RedisUtils redisUtils;

    /**
     * token过期时间
     */
    public static final long EXPIRE = 7 * 60 * 60 * 24;
    /**
     * 密钥(由公司设置)
     */
    public static final String SECRET = "GH_CREATE_JWT_IN_20221220";

    public String getAndCacheJwtToken(UserSessionDTO userSessionDTO) {
        // 缓存中获取token，其它设备登录过不需要重新生成token
        String token = (String) redisUtils.get(getTokenKey(userSessionDTO.getId()));
        if (StringUtils.isEmpty(token)) {
            token = getToken(userSessionDTO);
            boolean res = redisUtils.set(getTokenKey(userSessionDTO.getId()), token, EXPIRE);
            if (!res) {
                throw new ApiException(ApiExceptionEnum.DB_FAIL, "Redis缓存token失败");
            }
        }

        return token;
    }

    /**
     * 根据token获取会员信息
     */
    public static UserSessionDTO getUserByToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            return null;
        }
        try {
            DecodedJWT jwt = JWT.decode(jwtToken);
            String userJson = jwt.getClaim("user").asString();
            return JSON.parseObject(userJson, UserSessionDTO.class);
        } catch (JWTDecodeException e) {
            log.warn("Token解析失败");
            return null;
        }
    }

    /**
     * 判断token是否存在或有效
     */
    public boolean checkToken(String jwtToken) {
        UserSessionDTO user = getUserByToken(jwtToken);
        return user != null && jwtToken.equals(redisUtils.get(getTokenKey(user.getId())));
    }

    /**
     * 判断token是否存在或有效
     */
    public boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader(UserSessionDTO.HEADER_KEY);
            return checkToken(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public void removeToken(String userId) {
        redisUtils.del(getTokenKey(userId));
    }

    /**
     * 根据更新后的user字段选择更新token信息，字段值不为null则需要更新
     * 获取token及更新token两个操作需要保证原子性<<若为分布式需要使用分布式锁>>
     * @return 是否更新成功
     */
    public synchronized void refreshToken(UserDO userDO) {
        String token = (String) redisUtils.get(getTokenKey(userDO.getId()));
        // token为空说明未登录，无需更新
        if (token == null) {
            return;
        }

        UserSessionDTO sessionDTO = getUserByToken(token);
        // token解析错误
        if (sessionDTO == null) {
            throw new ApiException(ApiExceptionEnum.DB_FAIL, "Redis刷新token缓存失败，请手动刷新或删除userId为" + userDO.getId() + "的token");
        }
        BeanUtil.copyProperties(UserDO.toSessionDTO(userDO), sessionDTO, CopyOptions.create().ignoreNullValue());
        boolean res = redisUtils.set(getTokenKey(sessionDTO.getId()), getToken(sessionDTO), EXPIRE);
        if (!res) {
            throw new ApiException(ApiExceptionEnum.DB_FAIL, "Redis刷新token缓存失败，请手动刷新或删除userId为" + userDO.getId() + "的token");
        }
    }

    public static String getTokenKey(String userId) {
        return UserSessionDTO.KEY_USERID_PREFIX + "{" + userId + "}";
    }

    private String getToken(UserSessionDTO userSessionDTO) {
        Map<String, Object> headers = new HashMap<>(2);
        headers.put("typ", "JWT");
        headers.put("alg", "HMAC256");
        return JWT.create()
                .withHeader(headers)
                .withKeyId(userSessionDTO.getId())
                .withJWTId("jwt.seekers.ink")
                .withIssuer("www.seekers.ink")
                .withSubject("system-user" + userSessionDTO.getId())
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE))
                .withAudience(userSessionDTO.getId())
                //设置token主体部分
                .withClaim("user", JSON.toJSONString(userSessionDTO))
                .sign(Algorithm.HMAC256(SECRET + userSessionDTO.getId()));
    }
}
